package top.gokzzz.common.utils;

import org.slf4j.Logger;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Map;

/**
 * 異常 工具類
 *
 * @author Gok
 * @version 1.1
 */
public class ThrowableUtil {

    /**
     * 獲取堆棧信息
     *
     * @param t 错误/异常
     * @return {@link String }
     */
    public static String getStackTrace(Throwable t) {
        StringWriter sw = new StringWriter();
        try (PrintWriter pw = new PrintWriter(sw)) {
            t.printStackTrace(pw);
            return sw.toString();
        }
    }

    /**
     * 記錄格式化的異常信息到日誌
     *
     * @param logger     日誌對象
     * @param t          異常
     * @param message    自定義錯誤信息
     * @param contextMap 上下文參數映射，鍵為參數名，值為參數值
     */
    public static void error(Logger logger, Throwable t, String message, Map<String, Object> contextMap) {
        int lineLength = 150;
        String separator = String.join("", Collections.nCopies(lineLength, "="));
        String headerFooter = String.join("", Collections.nCopies(lineLength, "*"));

        StringBuilder sb = new StringBuilder();
        sb.append("\n\n").append(headerFooter).append("\n");
        sb.append(formatKeyValue("錯誤信息", message, lineLength)).append("\n");
        sb.append(separator).append("\n");

        // 獲取調用者的類名
        String callerClassName = getCallerClassName();
        sb.append(formatKeyValue("記錄類名", callerClassName, lineLength)).append("\n");
        sb.append(formatKeyValue("異常時間", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), lineLength)).append("\n");
        sb.append(formatKeyValue("異常類型", t.getClass().getName(), lineLength)).append("\n");
        sb.append(formatKeyValue("異常消息", t.getMessage(), lineLength)).append("\n");

        Throwable root = getRootCause(t);
        if (root != t) {
            sb.append(formatKeyValue("根本原因", root.getClass().getName() + ": " + root.getMessage(), lineLength)).append("\n");
        }

        // 打印自定義上下文參數
        if (contextMap != null && !contextMap.isEmpty()) {
            sb.append(separator).append("\n");
            sb.append("上下文參數:\n");

            for (Map.Entry<String, Object> entry : contextMap.entrySet()) {
                String paramName = entry.getKey();
                String paramValue = entry.getValue() != null ? String.valueOf(entry.getValue()) : "null";
                sb.append(formatKeyValue(paramName, paramValue, lineLength)).append("\n");
            }
        }

        sb.append(separator).append("\n");
        sb.append("堆疊追蹤:\n");

        StackTraceElement[] stackTrace = t.getStackTrace();
        final int MIN_ROW = 30;
        for (int i = 0; i < Math.min(MIN_ROW, stackTrace.length); i++) {
            StackTraceElement element = stackTrace[i];
            String line = String.format("    %2d | %s.%s(%s:%d)",
                    i + 1,
                    element.getClassName(),
                    element.getMethodName(),
                    element.getFileName(),
                    element.getLineNumber());
            sb.append(line).append("\n");
        }

        if (stackTrace.length > MIN_ROW) {
            sb.append(String.format("    ... 及其他 %d 行 ...\n", stackTrace.length - MIN_ROW));
        }

        sb.append(headerFooter).append("\n");

        logger.error(sb.toString());
    }

    /**
     * 獲取根本原因異常
     *
     * @param t 異常
     * @return 根本原因異常
     */
    public static Throwable getRootCause(Throwable t) {
        Throwable cause = t;
        while (cause.getCause() != null && cause.getCause() != cause) {
            cause = cause.getCause();
        }
        return cause;
    }


    /**
     * 格式化鍵值對顯示
     */
    private static String formatKeyValue(String key, String value, int lineLength) {
        String format = "%-15s : %s";
        String formattedStr = String.format(format, key, value);
        if (formattedStr.length() > lineLength - 5) {
            formattedStr = formattedStr.substring(0, lineLength - 8) + "...";
        }
        return formattedStr;
    }

    /**
     * 獲取調用方的類名
     *
     * @return 調用方的類名
     */
    private static String getCallerClassName() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        if (stackTraceElements.length > 3) {
            return stackTraceElements[3].getClassName();
        }
        return "未知";
    }
}
